---
import Layout from '../../../../layouts/Layout.astro';
import { readdir, readFile } from 'fs/promises';
import { join } from 'path';
export async function getStaticPaths() {
const paths: { params: { taxonomy: string; entry: string } }[] = [];
try {
const entriesPath = join(process.cwd(), 'src/content/entries');
const taxonomyDirs = await readdir(entriesPath);
for (const taxonomyDir of taxonomyDirs) {
try {
const taxonomyEntriesPath = join(entriesPath, taxonomyDir);
const entryFiles = await readdir(taxonomyEntriesPath);
const entries = entryFiles
.filter(file => file.endsWith('.md'))
.map(file => file.replace('.md', ''));
for (const entry of entries) {
paths.push({
params: {
taxonomy: taxonomyDir,
entry
},
});
}
} catch {
// Not a directory or no entries
}
}
} catch {
// Error reading directory
}
return paths;
}
const { taxonomy, entry } = Astro.params;
// Get ALL entries across all taxonomies for auto-linking (moved up before content processing)
let allEntries: Array<{slug: string, title: string, taxonomy: string, cleanTitle: string}> = [];
try {
const allEntriesPath = join(process.cwd(), 'src/content/entries');
const taxonomyDirs = await readdir(allEntriesPath);
for (const taxonomyDir of taxonomyDirs) {
try {
const taxonomyEntriesPath = join(allEntriesPath, taxonomyDir);
const files = await readdir(taxonomyEntriesPath);
for (const file of files) {
if (file.endsWith('.md')) {
const entrySlug = file.replace('.md', '');
let entryTitle = entrySlug.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
try {
const entryContent = await readFile(join(taxonomyEntriesPath, file), 'utf-8');
const titleMatch = entryContent.match(/^# (.+)$/m);
if (titleMatch) {
entryTitle = titleMatch[1];
}
} catch {
// Use default title
}
allEntries.push({
slug: entrySlug,
title: entryTitle,
taxonomy: taxonomyDir,
cleanTitle: entryTitle.toLowerCase()
});
}
}
} catch {
// Skip if not a directory
}
}
} catch {
// No entries found
}
// Read entry content
let entryContent = '';
let entryTitle = entry?.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) || 'Unknown Entry';
let taxonomyContext = '';
let articleType = 'full';
try {
const entryPath = join(process.cwd(), 'src/content/entries', taxonomy!, `${entry}.md`);
const rawContent = await readFile(entryPath, 'utf-8');
// Parse frontmatter
let contentWithoutFrontmatter = rawContent;
const frontmatterMatch = rawContent.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
if (frontmatterMatch) {
const frontmatterText = frontmatterMatch[1];
contentWithoutFrontmatter = frontmatterMatch[2];
// Parse frontmatter fields
const frontmatterLines = frontmatterText.split('\n');
for (const line of frontmatterLines) {
if (line.includes(':')) {
const [key, value] = line.split(':', 2);
const cleanKey = key.trim();
const cleanValue = value.trim().replace(/^["']|["']$/g, '');
if (cleanKey === 'article_type') {
articleType = cleanValue;
} else if (cleanKey === 'taxonomyContext') {
taxonomyContext = cleanValue;
}
}
}
}
// Extract title from markdown if present
const titleMatch = contentWithoutFrontmatter.match(/^# (.+)$/m);
if (titleMatch) {
entryTitle = titleMatch[1];
}
// Simple markdown to HTML conversion with auto-linking
entryContent = contentWithoutFrontmatter
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
.replace(/^\* (.+)$/gm, '<li>$1</li>')
.replace(/^- (.+)$/gm, '<li>$1</li>')
.replace(/(<li>.*<\/li>\n?)+/g, '<ul>$&</ul>')
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" style="color: #007acc; text-decoration: underline;">$1</a>')
.replace(/\n\n/g, '</p><p>')
.replace(/^(?!<)(.+)$/gm, '<p>$1</p>')
.replace(/<p><\/p>/g, '');
// Auto-link entry names - simple approach
for (const entryRef of allEntries) {
if (entryRef.slug !== entry && entryContent.includes(entryRef.title)) {
// Only process if the entry title appears in the content
const escapedTitle = entryRef.title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const linkRegex = new RegExp(`\\b${escapedTitle}\\b`, 'g');
// Simple replacement that avoids existing HTML tags
entryContent = entryContent.replace(linkRegex, (match) => {
// Don't replace if it's already a link or inside HTML tags
const beforeContext = entryContent.substring(0, entryContent.indexOf(match));
if (beforeContext.includes('<a ') && beforeContext.lastIndexOf('<a ') > beforeContext.lastIndexOf('</a>')) {
return match; // Already inside a link
}
if (beforeContext.includes('<') && beforeContext.lastIndexOf('<') > beforeContext.lastIndexOf('>')) {
return match; // Inside an HTML tag
}
return `<a href="/taxonomies/${entryRef.taxonomy}/entries/${entryRef.slug}" style="color: #007acc; text-decoration: underline;">${match}</a>`;
});
}
}
} catch {
entryContent = `<h1>${entryTitle}</h1><p>Entry content not found.</p>`;
}
// Check for entry image - images are copied to site during build
let entryImagePath = '';
// Look for entry images in the taxonomy subdirectory
entryImagePath = `/images/${taxonomy}/${entry}.png`;
// Get other entries in this taxonomy for navigation
let otherEntries: Array<{slug: string, title: string, isStub: boolean}> = [];
try {
const entriesPath = join(process.cwd(), 'src/content/entries', taxonomy!);
const files = await readdir(entriesPath);
for (const file of files) {
if (file.endsWith('.md') && file.replace('.md', '') !== entry) {
const entrySlug = file.replace('.md', '');
let entryTitle = entrySlug.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
let isStub = false;
try {
const entryContent = await readFile(join(entriesPath, file), 'utf-8');
// Parse frontmatter to get article_type
const frontmatterMatch = entryContent.match(/^---\n([\s\S]*?)\n---\n/);
if (frontmatterMatch) {
const frontmatterText = frontmatterMatch[1];
const frontmatterLines = frontmatterText.split('\n');
for (const line of frontmatterLines) {
if (line.includes(':')) {
const [key, value] = line.split(':', 2);
if (key.trim() === 'article_type' && value.trim().replace(/^["']|["']$/g, '') === 'stub') {
isStub = true;
break;
}
}
}
}
// Extract title from content
const titleMatch = entryContent.match(/^# (.+)$/m);
if (titleMatch) {
entryTitle = titleMatch[1];
}
} catch {
// Use default title if can't read file
}
otherEntries.push({ slug: entrySlug, title: entryTitle, isStub });
}
}
// Sort entries: full entries first, then stubs
otherEntries.sort((a, b) => {
if (a.isStub !== b.isStub) {
return a.isStub ? 1 : -1; // Full entries first
}
return a.title.localeCompare(b.title); // Alphabetical within each group
});
} catch {
// No other entries found
}
// Get world title from overview
let worldTitle = 'World';
try {
const overviewPath = join(process.cwd(), 'src/content/overview', 'world-overview.md');
const worldOverview = await readFile(overviewPath, 'utf-8');
const titleMatch = worldOverview.match(/^# (.+)$/m);
if (titleMatch) {
worldTitle = titleMatch[1];
}
} catch {
// Use default
}
const taxonomyTitle = taxonomy?.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) || 'Unknown Taxonomy';
---
<Layout title={`${entryTitle} - ${taxonomyTitle} - ${worldTitle}`} description={`${entryTitle} entry in ${taxonomyTitle}`}>
<div style="padding: 2rem; padding-bottom: 0;">
<div class="world-header">
<nav style="margin-bottom: 1rem;">
<a href="/" style="color: #666; text-decoration: none;">Home</a>
<span style="color: #ccc; margin: 0 0.5rem;">›</span>
<a href={`/taxonomies/${taxonomy}`} style="color: #666; text-decoration: none;">{taxonomyTitle}</a>
<span style="color: #ccc; margin: 0 0.5rem;">›</span>
<span style="color: #333;">
{entryTitle}
{articleType === 'stub' && <span style="color: #888; font-size: 0.9em;"> (stub)</span>}
</span>
</nav>
<h1>
{entryTitle}
{articleType === 'stub' && <span style="color: #888; font-size: 0.7em; font-weight: normal; margin-left: 0.5rem;">(stub)</span>}
</h1>
<p style="color: #666;">
{taxonomyTitle} in {worldTitle}
</p>
{articleType === 'stub' && (
<div style="background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 4px; padding: 0.75rem; margin: 1rem 0; color: #856404;">
<strong>📝 Stub Entry:</strong> This is a placeholder entry that needs further development and detail.
</div>
)}
</div>
{taxonomyContext && (
<div class="taxonomy-context">
<strong>Topic Context:</strong> {taxonomyContext}
</div>
)}
</div>
<div style="display: grid; gap: 2rem; grid-template-columns: 300px 1fr; margin-top: 2rem;">
<aside style="background: #f8f9fa; padding: 1.5rem; border-right: 1px solid #e0e0e0; height: 100vh; position: sticky; top: 0; overflow-y: auto;">
<h3 style="margin-top: 0;">Navigation</h3>
<div style="margin-bottom: 2rem;">
<a
href={`/taxonomies/${taxonomy}`}
style="color: #007acc; text-decoration: none; font-weight: 500;"
>
← Back to {taxonomyTitle}
</a>
</div>
{otherEntries.length > 0 && (
<div>
<h4>Other {taxonomyTitle} Entries</h4>
<ul style="list-style: none; padding: 0; max-height: 300px; overflow-y: auto;">
{otherEntries.map(otherEntry => (
<li style="margin: 0.5rem 0;">
<a
href={`/taxonomies/${taxonomy}/entries/${otherEntry.slug}`}
style="color: #007acc; text-decoration: none; font-size: 0.9em;"
>
{otherEntry.title}{otherEntry.isStub && ' (stub)'}
</a>
</li>
))}
</ul>
</div>
)}
</aside>
<article style="padding: 2rem;">
{entryImagePath && (
<div style="margin-bottom: 2rem; overflow: hidden; border-radius: 8px; max-width: 600px;">
<img
src={entryImagePath}
alt={`${entryTitle} illustration`}
style="width: 100%; height: auto;"
onError="this.style.display='none'"
/>
</div>
)}
<div set:html={entryContent} />
<div class="entry-meta">
<p>
<strong>Entry Type:</strong> {taxonomyTitle} •
<strong>Article Type:</strong> {articleType === 'stub' ? 'Stub Entry' : 'Full Entry'} •
<strong>World:</strong> {worldTitle}
</p>
</div>
</article>
</div>
</Layout>